package org.luxor.commons.playwright.component;

import cn.hutool.core.thread.ThreadUtil;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.options.Proxy;
import org.luxor.commons.playwright.config.properties.PlaywrightProperties;
import org.luxor.commons.playwright.constant.BrowserEnum;
import org.luxor.commons.playwright.constant.LaunchMode;
import org.luxor.commons.playwright.exception.CreatePlaywrightException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.nio.file.Paths;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;


/**
 * Web驱动创建器
 *
 * @author Mr.yan  @date 2022/4/8
 */
public class PlaywrightBrowserCreator {
    private static final Logger log = LoggerFactory.getLogger(PlaywrightBrowserCreator.class);
    private static final Pattern ENDPOINT_URL_PATTERN = Pattern.compile("^((http:\\/\\/|https:\\/\\/)?" +
            "(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])" +
            "(:\\d{1,5})$");

    private final static long DEFAULT_SLEEP_TIMEOUT = 200;

    private final BrowserConnectManager browserConnectManager;
    private final PlaywrightProperties properties;

    public PlaywrightBrowserCreator(PlaywrightProperties properties) {
        Assert.notNull(properties, "Spring容器中找不到[playwrightProperties]的实例化对象!");
        Assert.notNull(properties.getLaunchMode(), "浏览器启动模式[spring.playwright.launch-mode]配置参数，不能为空");
        Assert.notNull(properties.getBrowser(), "浏览器类型[spring.playwright.browser]配置参数，不能为空");

        if (properties.getSkipBrowserDownload()) {
            Assert.notNull(properties.getExecutablePath(), "当skipBrowserDownload=true时, 浏览器应用执行文件路径[spring.playwright.executablePath]配置参数，不能为空");
        }

        Assert.notNull(properties.getEndpoints(), "浏览器连接地址[spring.playwright.endpoints]配置参数，不能为空");
        for (String endpointUrl : properties.getEndpoints()) {
            if (!ENDPOINT_URL_PATTERN.matcher(endpointUrl).find()) {
                throw new IllegalArgumentException("浏览器连接地址[spring.playwright.endpoints]配置参数不合法. 错误项:" + endpointUrl);
            }
        }

        this.browserConnectManager = new BrowserConnectManager(properties.getEndpoints());
        this.properties = properties;
    }

    /**
     * 创建浏览器运行实例
     *
     * @return org.luPor.playwright.playwright
     */
    public synchronized Browser createBrowser() {
        log.info("Creator playwright browser Started...");

        Clock clock = Clock.systemDefaultZone();
        Duration timeout = Duration.ofSeconds(properties.getCreateBrowserTimeoutSeconds());
        Instant end = clock.instant().plus(timeout);
        while (true) {
            // 限制playwright最大可创建的数量，避免单台服务器运行过载
            if (browserConnectManager.inactiveCount() > 0) {
                String endpointUrl = browserConnectManager.getEndpointUrl();
                try {
                    Browser browser;
                    if (BrowserEnum.CHROME.equals(properties.getBrowser())) {
                        browser = getChrome(endpointUrl);
                    } else if (BrowserEnum.FIREFOX.equals(properties.getBrowser())) {
                        browser = getFirefox(endpointUrl);
                    } else if (BrowserEnum.WEBKIT.equals(properties.getBrowser())) {
                        browser = getWebkit(endpointUrl);
                    } else {
                        throw new CreatePlaywrightException("暂时不支持[" + properties.getBrowser().name() + "]浏览器类型");
                    }
                    browserConnectManager.active(browser, endpointUrl);
                    log.info("Creator browser completed. endpointUrl:[{}]", endpointUrl);
                    return browser;
                } catch (CreatePlaywrightException createException) {
                    log.error("Creator browser fail. endpointUrl:[" + endpointUrl + "]");
                    throw createException;
                } catch (Exception e) {
                    log.error("Creator browser fail. endpointUrl:[" + endpointUrl + "]");
                    throw new CreatePlaywrightException("Creator playwright browser fail.", e);
                }
            }

            // Check the timeout after evaluating the function to ensure conditions with a zero timeout can succeed.
            if (end.isBefore(clock.instant())) {
                throw new CreatePlaywrightException("Unable to create a new browser. " +
                        "The maximum number of browser exceeded [" + browserConnectManager.fullCount() + "].");
            }

            ThreadUtil.sleep(DEFAULT_SLEEP_TIMEOUT);
        }
    }

    private synchronized Browser getChrome(String endpointUrl) {
        Playwright.CreateOptions createOptions = getCreateOptions(properties);
        if (LaunchMode.LAUNCH.equals(properties.getLaunchMode())) {
            BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions();
            // 开启远程调试
            int ipBeginIndex = endpointUrl.lastIndexOf("/");
            int ipEndIndex = endpointUrl.lastIndexOf(":");
            String remoteDebuggingPort = "--remote-debugging-port=" + endpointUrl.substring(ipEndIndex + 1);
            String remoteDebuggingAddress = "--remote-debugging-address=" + endpointUrl.substring(ipBeginIndex + 1, ipEndIndex);
            // 禁用检查
            String disableBlinkFeatures = "--disable-blink-features";
            String disableBlinkFeaturesAutomationControlled = "--disable-blink-features=AutomationControlled";

            launchOptions.setArgs(Arrays.asList(remoteDebuggingPort, remoteDebuggingAddress, disableBlinkFeatures, disableBlinkFeaturesAutomationControlled));
            launchOptions.setChannel("chrome");
            // 代理服务器
            Proxy proxy = properties.getProxy();
            if (proxy != null) {
                launchOptions.setProxy(proxy);
            }
            // 启用无头模式?
            launchOptions.setHeadless(properties.getHeadless());
            // 设置下载目录
            launchOptions.setDownloadsPath(Paths.get(properties.getDownloadsPath()));
            // 指定浏览器应用的可执行文件路径
            if (properties.getSkipBrowserDownload()) {
                launchOptions.setExecutablePath(Paths.get(properties.getExecutablePath()));
            }
            return Playwright.create(createOptions).chromium().launch(launchOptions);
        } else {
            BrowserType.ConnectOverCDPOptions connectOverCDPOptions = new BrowserType.ConnectOverCDPOptions();
            Map<String, String> headers = new HashMap<String, String>(2);
            connectOverCDPOptions.setHeaders(headers);
            connectOverCDPOptions.setTimeout(Duration.ofSeconds(properties.getConnectTimeoutSeconds()).toMillis());
            return Playwright.create(createOptions).chromium().connectOverCDP(endpointUrl, connectOverCDPOptions);
        }
    }

    private synchronized Browser getFirefox(String endpointUrl) {
        Playwright.CreateOptions createOptions = getCreateOptions(properties);
        if (LaunchMode.LAUNCH.equals(properties.getLaunchMode())) {
            BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions();
            int ipBeginIndex = endpointUrl.lastIndexOf("/");
            int ipEndIndex = endpointUrl.lastIndexOf(":");
            String remoteDebuggingPort = "--remote-debugging-port=" + endpointUrl.substring(ipEndIndex + 1);
            String remoteDebuggingAddress = "--remote-debugging-address=" + endpointUrl.substring(ipBeginIndex + 1, ipEndIndex);
            launchOptions.setArgs(Arrays.asList(remoteDebuggingPort, remoteDebuggingAddress));
            // 代理服务器
            Proxy proxy = properties.getProxy();
            if (proxy != null) {
                launchOptions.setProxy(proxy);
            }
            // 启用无头模式?
            launchOptions.setHeadless(properties.getHeadless());
            // 设置下载目录
            launchOptions.setDownloadsPath(Paths.get(properties.getDownloadsPath()));
            // 指定浏览器应用的可执行文件路径
            if (properties.getSkipBrowserDownload()) {
                launchOptions.setExecutablePath(Paths.get(properties.getExecutablePath()));
            }
            return Playwright.create(createOptions).firefox().launch(launchOptions);
        } else {
            // 连接配置
            Map<String, String> headers = new HashMap<String, String>(2);
            BrowserType.ConnectOverCDPOptions connectOverCDPOptions = new BrowserType.ConnectOverCDPOptions();
            connectOverCDPOptions.setHeaders(headers);
            connectOverCDPOptions.setTimeout(Duration.ofSeconds(properties.getConnectTimeoutSeconds()).toMillis());
            return Playwright.create(createOptions).firefox().connectOverCDP(endpointUrl, connectOverCDPOptions);
        }
    }

    private synchronized Browser getWebkit(String endpointUrl) {
        Playwright.CreateOptions createOptions = getCreateOptions(properties);
        if (LaunchMode.LAUNCH.equals(properties.getLaunchMode())) {
            BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions();
            int ipBeginIndex = endpointUrl.lastIndexOf("/");
            int ipEndIndex = endpointUrl.lastIndexOf(":");
            String remoteDebuggingPort = "--remote-debugging-port=" + endpointUrl.substring(ipEndIndex + 1);
            String remoteDebuggingAddress = "--remote-debugging-address=" + endpointUrl.substring(ipBeginIndex + 1, ipEndIndex);
            launchOptions.setArgs(Arrays.asList(remoteDebuggingPort, remoteDebuggingAddress));
            // 代理服务器
            Proxy proxy = properties.getProxy();
            if (proxy != null) {
                launchOptions.setProxy(proxy);
            }
            // 启用无头模式?
            launchOptions.setHeadless(properties.getHeadless());
            // 设置下载目录
            launchOptions.setDownloadsPath(Paths.get(properties.getDownloadsPath()));
            // 浏览器可执行文件路径
            if (properties.getSkipBrowserDownload()) {
                launchOptions.setExecutablePath(Paths.get(properties.getExecutablePath()));
            }
            return Playwright.create(createOptions).webkit().launch(launchOptions);
        } else {
            BrowserType.ConnectOverCDPOptions connectOverCDPOptions = new BrowserType.ConnectOverCDPOptions();
            // 连接配置
            Map<String, String> headers = new HashMap<String, String>(2);
            connectOverCDPOptions.setHeaders(headers);
            connectOverCDPOptions.setTimeout(Duration.ofSeconds(properties.getConnectTimeoutSeconds()).toMillis());
            return Playwright.create(createOptions).webkit().connectOverCDP(endpointUrl, connectOverCDPOptions);
        }
    }

    /**
     * 关闭浏览器运行实列
     *
     * @param browser 浏览器连接
     */
    public synchronized void closeBrowser(Browser browser) {
        try {
            browserConnectManager.inactive(browser);
            browser.contexts().forEach(BrowserContext::close);
            browser.close();
        } catch (Throwable e) {
            log.warn("关闭浏览器异常", e);
        }
    }

    private synchronized Playwright.CreateOptions getCreateOptions(PlaywrightProperties playwrightProperties) {
        Map<String, String> env = new HashMap<>(5);

        // 跳过浏览器执行文件下载?
        if (playwrightProperties.getSkipBrowserDownload()) {
            env.put("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "1");
        }

        return new Playwright.CreateOptions().setEnv(env);
    }


}
